package org.wiztools.te;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Font;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import java.net.MalformedURLException;
import java.net.URL;

import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.prefs.Preferences;
import java.util.ResourceBundle;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.EditorKit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;

//import org.wiztools.util.gui.SplashScreen;
//import org.wiztools.util.gui.StaticGUIClass;
import org.wiztools.te.exception.TEFatalException;

public class TEFrame extends JFrame{
    
    private JFrame jf;
    private JLabel jl_status_west, jl_status_center, jl_status_east;
    
    private MyEditor me;
    private javax.swing.text.Document me_doc;
    
    private StringBuffer sberror;
    
    private JFileChooser jfc;
    
    private File file;
    
    private static URL configFileURL;
    
    private ClassLoader cl;
    
    // Processing elements hash map
    private HashMap<String, TEAction> hm_pe;
    
    // All TEProcess classes instantiated will be stored here.
    // During shutdown of the application, each of these's
    // destroy() method will be called.
    private List<TEProcess> al_tep;
    
    private TEMod tem;
    
    private KeyBinding kb;
    
    // private SplashScreen ss;
    private int splashCount;
    
    private UndoManager undo;
    
    // bufferChangedSinceLastSave variable is set to true whenever an edit happens
    private boolean bufferChangedSinceLastSave;
    
    private long lastSavedTime;
    
    // used to disable windowAdapter from firing when file open dialog is displayed
    private boolean fileOpenFlag;
    
    private Preferences prefs;
    
    private static boolean debugInfoOn;
    
    private static int instancesOpen;
    
    private static final ResourceBundle rb = ResourceBundle.getBundle("te");
    
    /**
     * Creates the set of command-line options
     * @return
     */
    private static Options generateOptions(){
        Options options = new Options();
        
        Option option = OptionBuilder.withLongOpt("debug").isRequired(false).withDescription("print stack trace (if any)").create('d');
        options.addOption(option);
        
        option = OptionBuilder.withLongOpt("config-xml").isRequired(false).hasArg().withDescription("load non-default configuration file").create('c');
        options.addOption(option);
        
        option = OptionBuilder.withLongOpt("mkdir").withDescription("create plugin and library directory").create("md");
        options.addOption(option);
        
        option = OptionBuilder.withLongOpt("help").isRequired(false).hasArg().withDescription("print this help").create('h');
        options.addOption(option);
        
        return options;
    }
    
    private static void printCommandLineHelp(Options options){
        HelpFormatter hf = new HelpFormatter();
        String cmdLine = "command [OPTIONS] LEFT_OVER";
        String descriptor = "Where [OPTIONS] could be zero or more of the following";
        String moreHelp = "Visit indiWiz.com for more help";
        hf.printHelp(cmdLine, descriptor, options, moreHelp);
    }
    
    public TEFrame(String fileName) throws TEFatalException{
        super(rb.getString("p-name"));
        
        synchronized(this){
            ++instancesOpen;
        }
        
        debugInfoOn = false;
        
        cl = this.getClass().getClassLoader();
        jf = this;
        
        jf.setIconImage(new ImageIcon(cl.getResource("org/wiztools/te/resource/logo.png")).getImage());
        
        //ss = new SplashScreen(this, new ImageIcon(cl.getResource("org/wiztools/te/resource/splash.jpg")), 7);
        //ss.setSplashColor(Color.WHITE, new Color(203,109,8));
        splashCount = 0;
        
        prefs = Preferences.userRoot().node("org/wiztools/te");
        
        //ss.setSplash("Loading File Dialogs", ++splashCount);
        File f = new File(prefs.get("file", System.getProperty("user.home")));
        jfc = new JFileChooser(f);
        
        kb = new KeyBinding();
        
        al_tep = new ArrayList<TEProcess>();
        // ShutdownHook
        Runtime.getRuntime().addShutdownHook(new Thread(){
            public void run(){
                for(TEProcess tept: al_tep){
                    tept.destroy();
                }
            }
        });
        
        //ss.setSplash("Creating Interface", ++splashCount);
        hm_pe = new HashMap<String, TEAction>();
        tem = new TEModify();
        
        sberror = new StringBuffer("");
        
        //ss.setSplash("Loading Actions", ++splashCount);
        loadCommonActions();
        //ss.setSplash("Building Layout", ++splashCount);
        buildLayout();
        
        this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        
        if(fileName!=null){
            file = new File(fileName);
            fileOpenFlag = true;
            if(file.exists() && file.isFile() && file.canRead()){
                read_f(file);
            } else{
                if(!file.exists()){
                    JOptionPane.showMessageDialog(this, "Creating a new file: "+file.getAbsolutePath());
                } else if(!file.isFile()){
                    JOptionPane.showMessageDialog(this, "Is not a valid text file: "+file.getAbsolutePath());
                    file = null;
                    fileOpenFlag = false;
                } else if(!file.canRead()){
                    JOptionPane.showMessageDialog(this, "Cannot read file: "+file.getAbsolutePath());
                    file = null;
                    fileOpenFlag = false;
                }
            }
        } else{
            fileOpenFlag = false;
        }
        
        this.addWindowListener(
                new WindowAdapter(){
            public void windowActivated(WindowEvent we){
                if(file==null || fileOpenFlag==true){
                    return;
                }
                fileOpenFlag = true;
                if(lastSavedTime<file.lastModified()){
                    Thread t = new Thread(){
                        public void run(){
                            lastSavedTime = file.lastModified();
                            int i = JOptionPane.showConfirmDialog(jf, "An External Application Modified The File! Reload The File?", "Reload File?", JOptionPane.YES_NO_OPTION);
                            if(i==JOptionPane.YES_OPTION){
                                read_f(file);
                            }
                        }
                    };
                    t.start();
                }
                fileOpenFlag = false;
            }
            public void windowClosing(WindowEvent we){
                exit();
            }
        }
        );
        
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        this.pack();
        Dimension currentD = this.getSize();
        this.setSize(currentD.width, d.height - 20);
        this.setVisible(true);
        //StaticGUIClass.showFrame(this,d.width,d.height);
        //ss.setVisible(false);
        me.requestFocus();
        
        if(!sberror.toString().trim().equals("")){
            ErrorDialog ed = new ErrorDialog(jf, "Startup Errors!");
            ed.setErrString(sberror.toString().trim());
            ed.setVisible(true);
        }
        
        // release resource
        kb = null;
        
        bufferChangedSinceLastSave = false;
    }
    
    /**
     * Method to parse the command line arguments
     * @param arg
     * @throws TEFatalException
     * @return The filenames to load
     */
    private static String[] commandLineParse(String[] arg) throws TEFatalException{
        Options options = generateOptions();
        try{
            CommandLineParser parser = new GnuParser();
            CommandLine cmd = parser.parse(options, arg);
            
            if(cmd.hasOption("md")){
                if(!DirectoryConstants.createDirs()){
                    System.err.println("An error occurred while creating directories!");
                    throw new TEFatalException("Application quitting...");
                }
                System.out.println("Directory creation (if it did not exist previously) successful.");
                System.exit(0);
            }
            
            if(cmd.hasOption('h')){
                printCommandLineHelp(options);
            }
            
            if(cmd.hasOption('d')){
                System.out.println("Debug Info is On! Stack Trace (if any) Will Be Printed Here!");
                debugInfoOn = true;
            }
            
            if(cmd.hasOption('c')){
                String configFile = cmd.getOptionValue('c');
                File f = new File(configFile);
                if(f.exists() && f.isFile() && f.canRead()){
                    configFileURL = f.toURL();
                } else{
                    throw new TEFatalException("Could not find/read the specified configuration file");
                }
            }
            
            String[] args = cmd.getArgs();
            return args;
        } catch(ParseException pe){
            System.err.println(pe.getMessage());
            printCommandLineHelp(options);
            throw new TEFatalException("Application quitting...");
        } catch(MalformedURLException mue){
            throw new TEFatalException(mue.toString()+"\nApplication quitting...");
        }
    }
    
    private void buildLayout(){
        Container ct = this.getContentPane();
        ct.setLayout(new BorderLayout());
        
        //ss.setSplash("Creating Editor", ++splashCount);
        
        me = new MyEditor();
        me.setFont(new Font(
                rb.getString("font_name"),
                Font.PLAIN,
                Integer.parseInt(
                rb.getString("font_size"))));
        loadDocumentListener();
        me.addCaretListener(
                new CaretListener(){
            public void caretUpdate(CaretEvent ce){
                // do nothing!
            }
        }
        );
        
        ct.add(new JScrollPane(me), BorderLayout.CENTER);
        
        //ss.setSplash("Loading Undo/Redo Logic", ++splashCount);
        
        // Undo
        loadUndo();
        loadUndoActions();
        
        //ss.setSplash("Creating Interface From XML", ++splashCount);
        // Interface from XML configuration
        buildInterface();
        
        // Status Bar
        JPanel jp_status = new JPanel();
        jp_status.setLayout(new BorderLayout());
        jl_status_west = new JLabel("");
        jp_status.add(jl_status_west, BorderLayout.WEST);
        jl_status_center = new JLabel("");
        jp_status.add(jl_status_center, BorderLayout.CENTER);
        jl_status_east = new JLabel("Welcome To The World Of WizTools.org!");
        jp_status.add(jl_status_east, BorderLayout.EAST);
        jp_status.setBorder(new BevelBorder(BevelBorder.LOWERED));
        
        ct.add(jp_status,BorderLayout.SOUTH);
    }
    
    private void loadUndo(){
        undo = new UndoManager();
        me_doc.addUndoableEditListener(
                new UndoableEditListener(){
            public void undoableEditHappened(UndoableEditEvent uee){
                undo.addEdit(uee.getEdit());
            }
        }
        );
    }
    
    private void loadCommonActions(){
        TEAction quitAction = new TEAction(){
            public void action(){
                tem.quit();
            }
        };
        hm_pe.put("quitAction", quitAction);
        
        TEAction saveFileAsAction = new TEAction(){
            public void action(){
                tem.saveFileAs();
                tem.requestFocus();
            }
        };
        hm_pe.put("saveFileAsAction", saveFileAsAction);
        
        TEAction saveFileAction = new TEAction(){
            public void action(){
                tem.saveFile();
                tem.requestFocus();
            }
        };
        hm_pe.put("saveFileAction", saveFileAction);
        
        TEAction openFileAction = new TEAction(){
            public void action(){
                tem.openFile();
                tem.requestFocus();
            }
        };
        hm_pe.put("openFileAction", openFileAction);
        
        TEAction newFileAction = new TEAction(){
            public void action(){
                tem.newFile();
                tem.requestFocus();
            }
        };
        hm_pe.put("newFileAction", newFileAction);
    }
    
    private void loadUndoActions(){
        TEAction undoAction = new TEAction(){
            public void action(){
                try{
                    undo.undo();
                } catch(CannotUndoException cue){
                    JOptionPane.showMessageDialog(jf, "Nothing to Undo!", "Nothing to Undo!", JOptionPane.INFORMATION_MESSAGE);
                }
            }
        };
        hm_pe.put("undoAction", undoAction);
        TEAction redoAction = new TEAction(){
            public void action(){
                try{
                    undo.redo();
                } catch(CannotRedoException cre){
                    JOptionPane.showMessageDialog(jf, "Nothing to Redo!", "Nothing to Redo!", JOptionPane.INFORMATION_MESSAGE);
                }
            }
        };
        hm_pe.put("redoAction", redoAction);
    }
    
    // called when opening a new document
    private void refreshDoc(){
        loadDocumentListener();
        loadUndo();
    }
    
    private void loadDocumentListener(){
        me_doc = me.getDocument();
        me_doc.addDocumentListener(
                new DocumentListener(){
            public void insertUpdate(javax.swing.event.DocumentEvent de){
                docChanged();
            }
            public void removeUpdate(javax.swing.event.DocumentEvent de){
                docChanged();
            }
            public void changedUpdate(javax.swing.event.DocumentEvent de){
                docChanged();
            }
        }
        );
    }
    
    private void docChanged(){
        bufferChangedSinceLastSave = true;
        jl_status_west.setText("*");
    }
    
    private void docSaved(){
        bufferChangedSinceLastSave = false;
        jl_status_west.setText("");
        if(file==null){
            jl_status_center.setText("");
        } else{
            String filePath = file.getAbsolutePath();
            jl_status_center.setText(filePath);
            String str = filePath.replaceFirst(System.getProperty("user.home"), "~");
            jf.setTitle(str+" - " + rb.getString("p-name"));
        }
    }
    
    private void buildInterface(){
        try{
            SAXBuilder builder = new SAXBuilder();
            
            Document doc;
            if(configFileURL==null){
                doc = builder.build(cl.getResource("interface.xml"));
            } else{
                doc = builder.build(configFileURL);
            }
            
            Element root = doc.getRootElement();
            java.util.List<Element> itr = getList(root);
            if(itr==null){
                return;
            }
            for(Element e: itr){
                String nme = e.getName();
                // create a new tab
                if(nme.equals("define-pe")){
                    loadPE(e);
                } else if(nme.equals("menu-bar")){
                    buildMenuBar(e);
                } else if(nme.equals("tabs")){
                    buildTabs(e);
                } else if(nme.equals("key-bind")){
                    loadKeyListeners(e);
                }
            }
        } catch(Exception e){
            System.out.println(e);
            debugInfo(e);
            System.exit(1);
        }
    }
    
    private TEAction getTEA(Element e){
        TEAction tea = null;
        String str = e.getName();
        String myStr = e.getTextTrim();
        if(str.equals("append")){
            tea = 	new TEAction(myStr){
                public void action(){
                    tem.append(this.str);
                    tem.requestFocus();
                }
            };
        } else if(str.equals("insert")){
            tea = 	new TEAction(myStr){
                public void action(){
                    tem.insert(this.str);
                    tem.requestFocus();
                }
            };
        } else if(str.equals("replace-selection")){
            tea =	new TEAction(myStr){
                public void action(){
                    tem.replaceSelection(this.str);
                    tem.requestFocus();
                }
            };
        } else if(str.equals("undo-edit")){
            tea = getPE("undoAction");
        } else if(str.equals("redo-edit")){
            tea = getPE("redoAction");
        } else if(str.equals("save-file-as")){
            tea = getPE("saveFileAsAction");
        } else if(str.equals("save-file")){
            tea = getPE("saveFileAction");
        } else if(str.equals("open-file")){
            tea = getPE("openFileAction");
        } else if(str.equals("new-file")){
            tea = getPE("newFileAction");
        } else if(str.equals("quit")){
            tea = getPE("quitAction");
        } else if(str.equals("process")){
            TEProcess tep = null;
            String classNme = e.getAttributeValue("class");
            if(classNme!=null){
                try{
                    //Class c_tep = Class.forName(classNme);
                    Class c_tep = TEPluginLoader.getInstance().getPluginClass(classNme);
                    Class[] class_tem = tem.getClass().getInterfaces();
                    //Constructor con = c_tep.getConstructor(new Class[]{class_tem[0]});
                    tep = (TEProcess)c_tep.newInstance();
                } catch(Exception ee){
                    sberror.append("\nError processing Action: ");
                    sberror.append(e.getAttributeValue("class"));
                    sberror.append(": ");
                    sberror.append(ee.toString());
                    debugInfo(ee);
                }
            }
            String scriptName = e.getAttributeValue("script");
            if(scriptName != null){
                try{
                    tep = BSHBuilder.getInstance().getTep(scriptName);
                } catch(Exception ee){
                    sberror.append("\nError processing Action: ");
                    sberror.append(e.getAttributeValue("class"));
                    sberror.append(": ");
                    sberror.append(ee.toString());
                    debugInfo(ee);
                }
            }
            if(tep != null){
                tep.setTEMod(tem);
                tep.init();
                tea = 	new TEAction(tep){
                    public void action(){
                        this.tep.action();
                    }
                };
                al_tep.add(tep);
            }
        } else if(str.equals("pe")){
            tea = getPE(e.getAttributeValue("name"));
        }
        
        return tea;
    }
    
    // Load processing elements
    private void loadPE(Element e){
        TEAction tea = null;
        String key = e.getAttributeValue("name");
        if(key==null){
            sberror.append("\nLoad PE Error: ``load-pe'' element's attribute ``name'' must have value: "+e.getName());
            return;
        }
        if(!key.matches("\\$\\{.+\\}")){
            sberror.append("\nLoad PE Error: ``load-pe'' element's attribute ``name'' must be of format ``${name}'': "+e.getName());
            return;
        }
        java.util.List<Element> itr = getList(e);
        if(itr==null){
            return;
        }
        for(Element e1: itr){
            tea = getTEA(e1);
        }
        
        hm_pe.put(key, tea);
    }
    
    private TEAction getPE(String key){
        return hm_pe.get(key);
    }
    
    private void buildMenuBar(Element e){
        java.util.List<Element> itr = getList(e);
        if(itr==null){
            return;
        }
        JMenuBar jmb = new JMenuBar();
        for(Element e1: itr){
            String str = e1.getName();
            if(str.equals("menu")){
                JMenu jm = buildMenu(e1);
                jmb.add(jm);
            }
        }
        
        this.setJMenuBar(jmb);
    }
    
    private JMenu buildMenu(Element e){
        JMenu jm = new JMenu(e.getAttributeValue("name"));
        String mne = e.getAttributeValue("mnemonic");
        if(mne!=null){
            if(mne.length()>0){
                jm.setMnemonic(mne.charAt(0));
            }
        }
        java.util.List<Element> itr = getList(e);
        if(itr==null){
            return jm;
        }
        for(Element e1: itr){
            String str = e1.getName();
            if(str.equals("menu-item")){
                JMenuItem jmi = buildMenuItem(e1);
                if(jmi!=null){
                    jm.add(jmi);
                }
            } else if(str.equals("menu")){
                JMenu jm1 = buildMenu(e1);
                if(jm1!=null){
                    jm.add(jm1);
                }
            } else if(str.equals("separator")){
                jm.addSeparator();
            } else{
                sberror.append("\nUnknown Element in Menu-Bar: "+str);
            }
        }
        
        return jm;
    }
    
    private JMenuItem buildMenuItem(Element e){
        JMenuItem jmi = new JMenuItem(e.getAttributeValue("name"));
        String mne = e.getAttributeValue("mnemonic");
        if(mne!=null){
            if(mne.length()>0){
                jmi.setMnemonic(mne.charAt(0));
            }
        }
        String icon = e.getAttributeValue("icon");
        if(icon!=null){
            jmi.setIcon(new ImageIcon(cl.getResource(icon)));
        }
        java.util.List<Element> itr = getList(e);
        if(itr==null){
            return jmi;
        }
        for(Element e1: itr){
            TEAction tea = getTEA(e1);
            if(tea!=null){
                jmi.addActionListener(tea);
            } else{
                sberror.append("\nInvalid Action for MenuItem: "+jmi.getText());
                return null;
            }
        }
        
        return jmi;
    }
    
    private void buildTabs(Element e){
        JTabbedPane jtp = new JTabbedPane();
        java.util.List<Element> itr = getList(e);
        if(itr==null){
            return;
        }
        for(Element e1: itr){
            if(e1.getName().equals("tab")){
                buildTab(e1, jtp);
            }
        }
        
        String addTo = e.getAttributeValue("add-to");
        String i = BorderLayout.NORTH;
        if(addTo!=null){
            if(addTo.equals("EAST")){
                i = BorderLayout.EAST;
            } else if(addTo.equals("WEST")){
                i = BorderLayout.WEST;
            }
        }
        this.getContentPane().add(jtp, i);
    }
    
    private void buildTab(Element e, JTabbedPane jtp){
        JPanel jp = new JPanel();
        jp.setLayout(new FlowLayout(FlowLayout.LEFT));
        
        java.util.List<Element> itr = getList(e);
        if(itr==null){
            return;
        }
        for(Element e1: itr){
            if(e1.getName().equals("button")){
                JButton jb = buildButton(e1);
                if(jb!=null){
                    jp.add(jb);
                }
            }
        }
        
        jtp.addTab(e.getAttributeValue("name"),jp);
    }
    
    private JButton buildButton(Element e){
        JButton jb = new JButton();
        buildButtonAttr(e, jb);
        java.util.List<Element> itr = getList(e);
        if(itr==null){
            return jb;
        }
        
        try{
            for(Element e1: itr){
                TEAction tea = getTEA(e1);
                if(tea!=null){
                    jb.addActionListener(tea);
                } else{
                    sberror.append("\nInvalid Action for Button: "+jb.getText());
                    return null;
                }
            }
        } catch(Exception ee){
            sberror.append("\nError processing button: ");
            sberror.append(e.getAttributeValue("name"));
            sberror.append(": ");
            sberror.append(ee.toString());
            debugInfo(ee);
        }
        return jb;
    }
    
    private void buildButtonAttr(Element e, JButton jb){
        String str;
        
        str = e.getAttributeValue("name");
        if(str!=null){
            jb.setText(str);
        }
        
        str = e.getAttributeValue("tooltip");
        if(str!=null){
            jb.setToolTipText(str);
        }
        
        str = e.getAttributeValue("mnemonic");
        if(str!=null){
            try{
                jb.setMnemonic(str.charAt(0));
            } catch(StringIndexOutOfBoundsException sioobe){
                System.out.println("Mnemonic not set for button! "+sioobe);
                debugInfo(sioobe);
            }
        }
        
        str = e.getAttributeValue("icon");
        if(str!=null){
            ImageIcon ii = new ImageIcon(str);
            jb.setIcon(ii);
        }
    }
    
    private void loadKeyListeners(Element e){
        TEAction tea = null;
        
        java.util.List<Element> itr = getList(e);
        if(itr==null){
            return;
        }
        for(Element e1: itr){
            tea = getTEA(e1);
        }
        
        int k = 0, m = 0;
        
        k = kb.getKeyCode(e.getAttributeValue("key"));
        m = kb.getKeyMask(e.getAttributeValue("mask"));
        
        if(tea!=null){
            if(k!=-1 || m!=-1){
                me.addKeyListener(null);
                me.registerKeyboardAction(tea, KeyStroke.getKeyStroke(k, m), JComponent.WHEN_FOCUSED);
            } else{
                sberror.append("\nUnknown Keybinding: "+e.getAttributeValue("key")+" + "+e.getAttributeValue("mask"));
            }
        } else{
            sberror.append("\nInvalid Action For Keybinding: "+e.getAttributeValue("key")+" + "+e.getAttributeValue("mask"));
        }
    }
    
    // Before loading fresh set of keyListeners, flush the old ones
    private void removeKeyListeners(){
        KeyStroke ks[] = me.getRegisteredKeyStrokes();
        for(int i=0;i<ks.length;i++){
            me.unregisterKeyboardAction(ks[i]);
        }
    }
    
    private java.util.List<Element> getList(Element e){
        if(e==null){
            return null;
        }
        java.util.List<Element> l = e.getChildren();
        if(l==null){
            return null;
        }
        return l;
    }
    
    private void open_f(){
        if(bufferChangedSinceLastSave){
            int i = askForSave();
            if(i==JOptionPane.CANCEL_OPTION){
                return;
            }
        }
        fileOpenFlag = true;
        int choice = jfc.showOpenDialog(jf);
        if(choice==JFileChooser.CANCEL_OPTION){
            fileOpenFlag = false;
            return;
        } else if(choice==JFileChooser.ERROR_OPTION){
            JOptionPane.showMessageDialog(jf,"An Error Occurred While Opening The File!","Error",JOptionPane.ERROR_MESSAGE);
            fileOpenFlag = false;
            return;
        }
        File f = jfc.getSelectedFile();
        read_f(f);
    }
    
    private void read_f(File f){
        try{
            me.read(new FileReader(f), null);
            file = f;
            docSaved();
            refreshDoc();
            lastSavedTime = file.lastModified();
            fileOpenFlag = false;
        } catch(IOException ioe){
            JOptionPane.showMessageDialog(jf,"An Error Occurred While Opening The File!","Error",JOptionPane.ERROR_MESSAGE);
        }
    }
    
    private void new_f(){
        if(!bufferChangedSinceLastSave){
            clear();
        } else{
            askForSave();
            clear();
        }
    }
    
    private int askForSave(){
        int i = JOptionPane.showConfirmDialog(jf,"File Not Saved! Save?","Save?",JOptionPane.YES_NO_CANCEL_OPTION);
        if(i==JOptionPane.NO_OPTION){
            clear();
        } else if(i==JOptionPane.YES_OPTION){
            save_f();
            clear();
        }
        return i;
    }
    
    private void clear(){
        file = null;
        tem.setText("");
        jl_status_west.setText("");
        jl_status_center.setText("");
        jf.setTitle(rb.getString("p-name"));
        refreshDoc();
        bufferChangedSinceLastSave = false;
        tem.requestFocus();
    }
    
    private void save_f(){
        if(file==null){
            saveAs_f();
        } else{
            writeFile();
            lastSavedTime = file.lastModified();
            docSaved();
        }
    }
    
    private void saveAs_f(){
        int choice = jfc.showSaveDialog(jf);
        if(choice==JFileChooser.CANCEL_OPTION){
            return;
        } else if(choice==JFileChooser.ERROR_OPTION){
            JOptionPane.showMessageDialog(jf,"An Error Occurred While Saving The File!","Error",JOptionPane.ERROR_MESSAGE);
            return;
        }
        File f = jfc.getSelectedFile();
        if(f==null){
            return;
        }
        if(f.exists()){
            int i = JOptionPane.showConfirmDialog(jf,"File Already Exists. Overwrite?","Overwrite?",JOptionPane.YES_NO_OPTION);
            if(i==JOptionPane.NO_OPTION){
                return;
            }
        }
        file = f;
        writeFile();
        lastSavedTime = file.lastModified();
        docSaved();
    }
    
    private void writeFile(){
        try{
            me.write(new FileWriter(file));
        } catch(IOException ioe){
            JOptionPane.showMessageDialog(jf,"An Error Occurred While Saving The File!","Error",JOptionPane.ERROR_MESSAGE);
        }
    }
    
    private void debugInfo(Exception e){
        if(debugInfoOn){
            System.err.println("\n=====================\nStack Trace Of Error:");
            e.printStackTrace();
        }
    }
    
    private void exit(){
        if(bufferChangedSinceLastSave){
            int i = JOptionPane.showConfirmDialog(jf,"Save File Before Quitting?","Save File?",JOptionPane.YES_NO_CANCEL_OPTION);
            if(i==JOptionPane.YES_OPTION){
                save_f();
            } else if(i==JOptionPane.CANCEL_OPTION){
                return;
            }
        }
        if(file!=null){
            prefs.put("file", file.getAbsolutePath());
        }
        synchronized(this){
            --instancesOpen;
        }
        jf.setVisible(false);
        jf.invalidate();
        if(instancesOpen==0){
            System.exit(0);
        }
    }
    
    public class TEModify implements TEMod{
        private final MyDialog jd;
        TEModify(){
            jd = new MyDialog(jf);
        }
        public final JDialog getJD(){
            return jd;
        }
        public final String getText(){
            return me.getText();
        }
        public final String getSelectedText(){
            return me.getSelectedText();
        }
        
        // To Be Called By Developer!
        public final void setText(String str){
            me.setText(str);
        }
        public final void insert(String str){
            me.insert(str);
        }
        public final void replaceSelection(String str){
            if(getSelectedText()!=null){
                str = str.replaceAll("\\$\\{selection\\}",getSelectedText());
            } else{
                str = str.replaceAll("\\$\\{selection\\}","");
            }
            me.replaceSelection(str);
        }
        
        public final void setSelectionStart(int i){
            me.setSelectionStart(i);
        }
        
        public final void setSelectionEnd(int i){
            me.setSelectionEnd(i);
        }
        
        public final int getSelectionStart(){
            return me.getSelectionStart();
        }
        
        public final int getSelectionEnd(){
            return me.getSelectionEnd();
        }
        
        public final void selectAll(){
            me.selectAll();
        }
        
        public final void append(String str){
            me.append(str);
        }
        
        public final int getCaretPosition(){
            return me.getCaretPosition();
        }
        public final void setCaretPosition(int i){
            me.setCaretPosition(i);
        }
        public final int getLineCount(){
            return me.getLineCount();
        }
        
        public final void requestFocus(){
            me.requestFocus();
        }
        
        // File Dialogs
        public final void saveFile(){
            save_f();
        }
        
        public final void saveFileAs(){
            saveAs_f();
        }
        
        public final void openFile(){
            open_f();
        }
        
        public final void newFile(){
            new_f();
        }
        
        public final void quit(){
            exit();
        }
        
        // Dialog Operations
        public final void dispDialog(Container ct, String title){
            jd.setContentPane(ct);
            jd.setTitle(title);
            jd.pack();
            Point p = jf.getLocation();
            jd.setLocation(p.x+(jf.getWidth()-jd.getWidth())/2, p.y+(jf.getHeight()-jd.getHeight())/2);
            jd.setVisible(true);
        }
        
        public final void closeDialog(){
            jd.setVisible(false);
        }
        
        class MyDialog extends JDialog{
            MyDialog(JFrame jf){
                super(jf);
            }
            protected JRootPane createRootPane() {
                ActionListener actionListener = new ActionListener() {
                    public void actionPerformed(ActionEvent actionEvent) {
                        setVisible(false);
                    }
                };
                JRootPane rootPane = new JRootPane();
                rootPane.registerKeyboardAction(actionListener, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
                return rootPane;
            }
        }

        public void setEditorSyntax(EditorKit lang) {
            String str = me.getText();
            me.setEditorKit(lang);
            me.setText(str);
        }
    }
    
    public abstract class TEAction implements ActionListener{
        
        // These have to be public, because this is accessed from outside this class.
        public String str;
        public TEProcess tep;
        
        public TEAction(){
            // do nothing!
        }
        
        public TEAction(TEProcess tep){
            this.tep = tep;
        }
        
        public TEAction(String str){
            this.str = str;
        }
        
        abstract public void action();
        
        final public void actionPerformed(ActionEvent ae){
            action();
        }
    }
    
    public static void main(final String[] arg){
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try{
                    String[] files = commandLineParse(arg);
                    if(files.length!=0){
                        for(int i=0;i<files.length;i++){
                            new TEFrame(files[i]);
                        }
                    } else{
                        new TEFrame(null);
                    }
                } catch(TEFatalException tefe){
                    System.err.println(tefe.getMessage());
                    System.exit(1);
                }
            }
        });
    }
}
